import sys
import threading
import time
import serial
import serial.tools.list_ports
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText


class SerialBurnerApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("智能串口烧录工具")
        self.geometry("800x600")
        self.resizable(True, True)

        # 初始化变量
        self.serial_port = None
        self.burning_flag = False
        self.file_path = ""
        self.baudrates = [9600, 19200, 38400, 57600, 115200, 230400, 460800]
        self.last_ports = []

        self.setup_ui()
        self.refresh_ports()
        self.start_auto_refresh()

        self.protocol("WM_DELETE_WINDOW", self.on_close)

    def setup_ui(self):
        """初始化界面组件"""
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 控制面板
        control_frame = ttk.LabelFrame(main_frame, text="设备控制", padding=10)
        control_frame.pack(fill=tk.X, pady=5)

        # 串口配置
        ttk.Label(control_frame, text="可用端口:").grid(row=0, column=0, sticky=tk.W)
        self.port_combo = ttk.Combobox(control_frame, state="readonly", width=18)
        self.port_combo.grid(row=0, column=1, padx=5)

        ttk.Label(control_frame, text="波特率:").grid(row=0, column=2, sticky=tk.W)
        self.baud_combo = ttk.Combobox(control_frame, values=self.baudrates, width=10)
        self.baud_combo.set(115200)
        self.baud_combo.grid(row=0, column=3, padx=5)

        # 文件选择
        self.file_entry = ttk.Entry(control_frame, width=35)
        self.file_entry.grid(row=0, column=4, padx=5)
        ttk.Button(control_frame, text="选择文件", command=self.select_file).grid(row=0, column=5)

        # 操作按钮
        btn_frame = ttk.Frame(control_frame)
        btn_frame.grid(row=0, column=6, padx=10)
        self.connect_btn = ttk.Button(btn_frame, text="打开串口", command=self.toggle_serial, width=8)
        self.connect_btn.pack(side=tk.LEFT, padx=2)
        self.start_btn = ttk.Button(btn_frame, text="开始烧录", command=self.start_burn, width=8)
        self.start_btn.pack(side=tk.LEFT, padx=2)
        self.stop_btn = ttk.Button(btn_frame, text="停止", state=tk.DISABLED, command=self.stop_burn, width=6)
        self.stop_btn.pack(side=tk.LEFT, padx=2)

        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="系统日志", padding=10)
        log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        self.log_text = ScrolledText(log_frame, wrap=tk.WORD, state='disabled')
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # 进度条
        self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, mode='determinate')
        self.progress.pack(fill=tk.X, pady=5)

        # 状态栏
        self.status_var = tk.StringVar()
        status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN)
        status_bar.pack(fill=tk.X)

    # 新增的关键方法定义
    def select_file(self):
        """选择烧录文件"""
        path = filedialog.askopenfilename(filetypes=[
            ("二进制文件", "*.bin *.hex"),
            ("所有文件", "*.*")
        ])
        if path:
            self.file_path = path
            self.file_entry.delete(0, tk.END)
            self.file_entry.insert(0, path)
            self.update_status(f"已选择文件: {path}")

    def toggle_serial(self):
        """切换串口连接状态"""
        if self.serial_port and self.serial_port.is_open:
            self.close_serial()
        else:
            self.open_serial()

    def open_serial(self):
        """打开串口连接"""
        port = self.port_combo.get()
        baud = int(self.baud_combo.get())

        if not port:
            messagebox.showwarning("警告", "请先选择串口！")
            return

        try:
            self.serial_port = serial.Serial(
                port=port,
                baudrate=baud,
                timeout=1
            )
            self.connect_btn.config(text="关闭串口")
            self.log(f"已打开串口: {port} @ {baud}bps")
            self.update_status("串口已连接")
            self.toggle_controls(True)
        except Exception as e:
            messagebox.showerror("错误", f"无法打开串口: {str(e)}")

    def close_serial(self):
        """关闭串口连接"""
        if self.serial_port:
            self.serial_port.close()
            self.serial_port = None
        self.connect_btn.config(text="打开串口")
        self.log("串口已关闭")
        self.update_status("串口已断开")
        self.toggle_controls(False)

    def start_auto_refresh(self):
        """启动自动端口检测"""
        self.refresh_ports()
        self.after(1000, self.start_auto_refresh)

    def refresh_ports(self):
        """刷新可用串口列表"""
        try:
            current_ports = list(serial.tools.list_ports.comports())
            current_port_names = [p.device for p in current_ports]

            if current_port_names != self.last_ports:
                self.last_ports = current_port_names
                previous_selection = self.port_combo.get()

                self.port_combo['values'] = current_port_names

                if previous_selection in current_port_names:
                    self.port_combo.set(previous_selection)
                elif current_port_names:
                    self.port_combo.current(0)
                else:
                    self.port_combo.set('')

                status = f"检测到 {len(current_ports)} 个串口设备" if current_ports else "未检测到可用串口"
                self.update_status(status)
        except Exception as e:
            self.log(f"端口检测错误: {str(e)}")

    def start_burn(self):
        """开始烧录流程"""
        if not self.validate_inputs():
            return

        self.burning_flag = True
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.progress['value'] = 0

        threading.Thread(target=self.burn_process, daemon=True).start()

    def burn_process(self):
        """烧录线程主逻辑"""
        try:
            with open(self.file_path, 'rb') as f:
                data = f.read()
                total = len(data)
                chunk_size = 128

                for i in range(0, total, chunk_size):
                    if not self.burning_flag:
                        break

                    chunk = data[i:i + chunk_size]
                    self.serial_port.write(chunk)

                    progress = (i + chunk_size) / total * 100
                    self.update_progress(progress)
                    self.log(f"已发送 {i + len(chunk)}/{total} 字节")

                    time.sleep(0.01)

                if self.burning_flag:
                    self.log("烧录完成")
                    self.update_status("烧录成功")
                else:
                    self.log("烧录已中止")

        except Exception as e:
            self.log(f"错误: {str(e)}")
            messagebox.showerror("错误", str(e))
        finally:
            self.after(0, self.reset_controls)

    # 其他辅助方法
    def validate_inputs(self):
        """验证输入有效性"""
        if not self.serial_port or not self.serial_port.is_open:
            messagebox.showwarning("警告", "请先打开串口！")
            return False
        if not self.file_path:
            messagebox.showwarning("警告", "请选择烧录文件！")
            return False
        return True

    def update_progress(self, value):
        """更新进度条"""
        self.after(0, lambda: self.progress.configure(value=min(value, 100)))

    def log(self, message):
        """记录日志信息"""
        self.after(0, lambda: self.log_text.configure(state='normal'))
        self.after(0, lambda: self.log_text.insert(tk.END, f"[{time.strftime('%H:%M:%S')}] {message}\n"))
        self.after(0, lambda: self.log_text.configure(state='disabled'))
        self.after(0, lambda: self.log_text.see(tk.END))

    def update_status(self, message):
        """更新状态栏"""
        self.status_var.set(message)

    def toggle_controls(self, connected):
        """切换控件状态"""
        state = tk.NORMAL if connected else tk.DISABLED
        self.start_btn.config(state=state)
        self.port_combo.config(state=state)
        self.baud_combo.config(state=state)

    def reset_controls(self):
        """重置控件状态"""
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.burning_flag = False

    def stop_burn(self):
        """停止烧录操作"""
        self.burning_flag = False
        self.log("正在停止烧录...")
        self.update_status("烧录已中止")

    def on_close(self):
        """处理窗口关闭事件"""
        if self.burning_flag:
            if messagebox.askokcancel("退出", "烧录正在进行中，确定要退出吗？"):
                self.destroy()
        else:
            self.destroy()


if __name__ == "__main__":
    app = SerialBurnerApp()
    app.mainloop()